﻿//*** Guidance generated code ***//
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

using ProjectBase.Core;

using $prjsn$.Entity;

namespace $safeprojectname$
{
    public class MetaDataReflector<TEntity, TID>
        where TEntity : class, IPersistentObjectWithTypedId<TID>
    {
        public virtual MetaDataMessage ReadMetaData(IList<MappingDescription> mappingInfo, IList<string> alwaysIgnoredProperties)
        {
            var metaData = new MetaDataMessage
            {
                EntityName = typeof(TEntity).Name,
                CanRead = true,
                CanEdit = true,
                IgnoredProperties = alwaysIgnoredProperties,
            };
            if (typeof(TEntity).HasType<IHaveHierarchy>())
            {
                metaData.EntityType = "Hierarchy";
            }
            else if (typeof(TEntity).HasType<ICodeList>())
            {
                metaData.EntityType = "CodeList";
            }
            else if (typeof(TEntity).HasType<IPersistentObject>())
            {
                metaData.EntityType = "BusinessObject";
            }

            ReadAttributesFromProperties(mappingInfo, metaData);

            return metaData;
        }

        protected virtual void ReadAttributesFromProperties(IList<MappingDescription> mappingInfo, MetaDataMessage metaData)
        {
            foreach (var propertyInfo in typeof(TEntity).GetProperties())
            {
                if (metaData.IgnoredProperties.Contains(propertyInfo.Name))
                {
                    continue;
                }

                MDAttribute attribute = ReadAttribute(mappingInfo, propertyInfo);

                if (attribute.IsNull())
                {
                    continue;
                }

                metaData.Attributes.Add(attribute);
            }
        }

        protected virtual MDAttribute ReadAttribute(IList<MappingDescription> mappingInfo, PropertyInfo propertyInfo)
        {
            var attribute = new MDAttribute
            {
                Name = propertyInfo.Name,
                DisplayName = propertyInfo.Name.ToSentence(),

                CanQuery = propertyInfo.CanWrite, // read only property does not support querying
                CanRead = true,
                CanEdit = propertyInfo.CanWrite,

                Type = typeof(string),

                IsNullable = true,
            };

            if (mappingInfo.IsNotEmpty())
            {
                var map = mappingInfo.SingleOrDefault(m => m.PropertyName.Equals(propertyInfo.Name));
                if (map.Is())
                {
                    // the formula mapping
                    attribute.PersistenceName = (map.PersistenceName ?? string.Empty)
                                                   .Replace("[", string.Empty)
                                                   .Replace("]", string.Empty);
                    attribute.CanEdit = map.CanUpdate;
                    attribute.MappingInfo = map;
                }
            }

            var isNotNullable = propertyInfo.PropertyType.IsValueType
                            && !propertyInfo.PropertyType.IsGenericType;
            if (isNotNullable)
            {
                attribute.IsNullable = false;
            }

            var isResolved = IsString(propertyInfo, attribute)
                             || IsValueType(propertyInfo, attribute)
                             || IsPersistent(propertyInfo, attribute)
                             || IsArray(propertyInfo, attribute)
                             || IsAdditional(propertyInfo, attribute)
                             ;
            if (!isResolved)
            {
                return null;
            }
            return attribute;
        }

        protected virtual bool IsString(PropertyInfo propertyInfo, MDAttribute attribute)
        {
            var type = propertyInfo.PropertyType;

            var isString = type.IsEquivalentTo(typeof(string));

            if (!isString)
            {
                return false;
            }

            attribute.Type = typeof(string);
            return true;
        }

        protected virtual bool IsValueType(PropertyInfo propertyInfo, MDAttribute attribute)
        {
            var type = propertyInfo.PropertyType;

            if (!type.IsValueType)
            {
                return false;
            }

            attribute.IsNullable = type.IsGenericType;

            // Bool
            var isBool = type.IsEquivalentTo(typeof(bool))
                            || type.IsEquivalentTo(typeof(bool?));

            if (isBool)
            {
                attribute.Type = typeof(bool);
                return true;
            }

            // DateTime
            var isDateTime = type.IsEquivalentTo(typeof(DateTime))
                            || type.IsEquivalentTo(typeof(DateTime?));

            if (isDateTime)
            {
                attribute.Type = typeof(DateTime);
                return true;
            }

            // Decimal
            var isDecimal = type.IsEquivalentTo(typeof(decimal))
                            || type.IsEquivalentTo(typeof(decimal?));

            if (isDecimal)
            {
                attribute.Type = typeof(decimal);
                return true;
            }

            // Integer
            var isInteger = type.IsEquivalentTo(typeof(int))
                            || type.IsEquivalentTo(typeof(int?));

            if (isInteger)
            {
                if (propertyInfo.Name.EndsWith("Id"))
                {
                    attribute.CanEdit = true;
                }
                attribute.Type = typeof(int);
                return true;
            }

            return false;
        }

        protected virtual bool IsArray(PropertyInfo propertyInfo, MDAttribute attribute)
        {
            var type = propertyInfo.PropertyType;

            var isArray = type.IsGenericType
                          && type.HasType<IEnumerable>();

            if (!isArray)
            {
                return false;
            }

            attribute.IsArray = true;
            attribute.CanQuery = false;

            // the type of the collection item <T>
            var itemType = type.GetGenericArguments()[0];

            attribute.Type = itemType;

            return true;
        }

        protected virtual bool IsPersistent(PropertyInfo propertyInfo, MDAttribute attribute)
        {
            var type = propertyInfo.PropertyType;

            var isPersistent = type.HasType<IPersistentObject>();
            if (!isPersistent)
            {
                return false;
            }

            // e.g. the Parent
            attribute.Name = propertyInfo.Name;
            attribute.DisplayName = attribute.Name.ToSentence();
            attribute.Type = type;
            attribute.CanQuery = true;
            attribute.CanRead = true;
            attribute.CanEdit = true;
            attribute.IsEntityReference = true;

            return true;
        }

        protected virtual bool IsAdditional(PropertyInfo propertyInfo, MDAttribute attribute)
        {
            var map = attribute.MappingInfo;
            var isAdditional = map.Is()
                               && map.IsDynamic;

            if (!isAdditional)
            {
                return false;
            }

            attribute.IsAdditional = true;
            attribute.IsArray = false;
            attribute.CanQuery = true;
            attribute.CanRead = true;
            attribute.CanEdit = true;
            attribute.Type = typeof(object);

            var additionals = new List<MDAttribute>();
            foreach (var mappingDescription in map.DynamicComponents)
            {
                var attr = CreateAdditionalAttribute(mappingDescription);
                if (attr.Is())
                {
                    additionals.Add(attr);
                }
            }

            if (additionals.IsNotEmpty())
            {
                attribute.Attributes = additionals;
            }
            return true;
        }

        protected virtual MDAttribute CreateAdditionalAttribute(IMappingDescription map)
        {
            var attribute = new MDAttribute
            {
                Name = map.PropertyName,
                PersistenceName = map.PersistenceName,

                Type = map.PropertyType,

                CanQuery = true,
                CanEdit = map.CanUpdate,

                IsEntityReference = map.PropertyType.HasType<IPersistentObject>(),
                IsNullable = true,
                DisplayName = map.PropertyName.ToSentence(),
            };

            return attribute;
        }
    }
}